#define _XOPEN_SOURCE 700

#include "nfs.h"

/******************************************************************************
 * SECTION: 宏定义
 *******************************************************************************/
#define OPTION(t, p) {t, offsetof(struct custom_options, p), 1}

/******************************************************************************
 * SECTION: 全局变量
 *******************************************************************************/
static const struct fuse_opt option_spec[] = {/* 用于FUSE文件系统解析参数 */
                                              OPTION("--device=%s", device), FUSE_OPT_END};

struct custom_options nfs_options; /* 全局选项 */
struct nfs_super nfs_super;

/******************************************************************************
 * SECTION: FUSE操作定义
 *******************************************************************************/
static struct fuse_operations operations = {.init = nfs_init,       /* mount文件系统 */
                                            .destroy = nfs_destroy, /* umount文件系统 */
                                            .mkdir = nfs_mkdir,     /* 建目录，mkdir */
                                            .getattr = nfs_getattr, /* 获取文件属性，类似stat，必须完成 */
                                            .readdir = nfs_readdir, /* 填充dentrys */
                                            .mknod = nfs_mknod,     /* 创建文件，touch相关 */
                                            .write = NULL,          /* 写入文件 */
                                            .read = NULL,           /* 读文件 */
                                            .utimens = nfs_utimens, /* 修改时间，忽略，避免touch报错 */
                                            .truncate = NULL,       /* 改变文件大小 */
                                            .unlink = NULL,         /* 删除文件 */
                                            .rmdir = NULL,          /* 删除目录， rm -r */
                                            .rename = NULL,         /* 重命名，mv */
                                            .open = NULL,
                                            .opendir = NULL,
                                            .access = NULL};

/******************************************************************************
 * SECTION: 必做函数实现
 *******************************************************************************/
/**
 * @brief 挂载（mount）文件系统
 *
 * @param conn_info 可忽略，一些建立连接相关的信息
 * @return void*
 */
void *nfs_init(struct fuse_conn_info *conn_info)
{
    /* TODO: 在这里进行挂载 */
    if (nfs_mount(nfs_options) != NFS_ERROR_NONE)
    {
        SFS_DBG("[%s] mount error\n", __func__);
        fuse_exit(fuse_get_context()->fuse);
        return NULL;
    }

    return NULL;

    ///* 下面是一个控制设备的示例 */
    // nfs_super.fd = ddriver_open(options.device);
}

/**
 * @brief 卸载（umount）文件系统
 *
 * @param p 可忽略
 * @return void
 */
void nfs_destroy(void *p)
{
    /* TODO: 在这里进行卸载 */
    if (nfs_umount() != NFS_ERROR_NONE)
    {
        SFS_DBG("[%s] mount error\n", __func__);
        fuse_exit(fuse_get_context()->fuse);
        return;
    }

    return;

    // ddriver_close(super.fd);
}

/**
 * @brief 创建目录
 *
 * @param path 相对于挂载点的路径
 * @param mode 创建模式（只读？只写？），可忽略
 * @return int 0成功，否则失败
 */
int nfs_mkdir(const char *path, mode_t mode)
{
    /* TODO: 解析路径，创建目录 */
    boolean is_root, is_find;
    char *fname;
    struct nfs_dentry *lastdentry = nfs_lookup(path, &is_find, &is_root);
    struct nfs_inode *lastinode = lastdentry->inode_pt;
    struct nfs_dentry *dentry;

    // 目录已经存在
    if (is_find)
    {
        return -EEXIST;
    }
    
    // 最后一级目录是文件，无法创建目录
    if (lastinode->type == NFS_FILE)
    {
        return -ENXIO;
    }

    fname = nfs_get_fname(path);
    dentry = new_dentry(fname, NFS_DIR, -1);
    nfs_alloc_inode(dentry, NFS_DIR);

    // lastinode 增加一个目录项
    int dirs_per_blk = SZ_BLK / sizeof(struct nfs_dentry_d); // 一块能够存放的目录项数量
    lastinode->dircnt++;
    // 为该 inode 的第一个目录项或当前数据块已满
    if (lastinode->dircnt == 1 || ((lastinode->dircnt - 1) / dirs_per_blk != (lastinode->dircnt - 2) / dirs_per_blk))
    {
        // 需要新的数据块来存放增加的目录项
        int blkno = (lastinode->dircnt - 1) / dirs_per_blk;
        if (blkno >= NFS_DATA_PER_FILE)
        {
            return -1;
        }
        char *bpt;
        int bno;
        nfs_alloc_dblk(&bpt, &bno);
        lastinode->datablock[blkno] = bno;
        lastinode->datablock_pt[blkno] = bpt;
    }

    // 头插法插入链表，建立父指针
    dentry->nxt = lastinode->dentry_sons;
    lastinode->dentry_sons = dentry;
    dentry->fa = lastdentry;

    return NFS_ERROR_NONE;
}

/**
 * @brief 获取文件或目录的属性，该函数非常重要
 *
 * @param path 相对于挂载点的路径
 * @param nfs_stat 返回状态
 * @return int 0成功，否则失败
 */
int nfs_getattr(const char *path, struct stat *nfs_stat)
{
    /* TODO: 解析路径，获取Inode，填充newfs_stat，可参考/fs/simplefs/nfs.c的nfs_getattr()函数实现 */
    boolean is_find, is_root;
    struct nfs_dentry *dentry = nfs_lookup(path, &is_find, &is_root);
    struct nfs_inode *inode = dentry->inode_pt;

    if (!is_find)
    {
        return -ENOENT;
    }

    // 结构体填充
    if (dentry->type == NFS_DIR)
    {
        nfs_stat->st_mode = S_IFDIR | NFS_DEFAULT_PERM; // 文件的模式，包括文件的权限、文件类型
        nfs_stat->st_size = inode->dircnt * sizeof(struct nfs_dentry_d); // 文件的大小
    }
    else if (dentry->type == NFS_FILE)
    {
        nfs_stat->st_mode = S_IFREG | NFS_DEFAULT_PERM;
        nfs_stat->st_size = inode->size;
    }
    else
    {
        SFS_DBG("[%s] mgetattr: incorrect file type!\n", __func__);
    }

    nfs_stat->st_nlink = 1;          // 文件的连接数，即多少硬链接
    nfs_stat->st_uid = getuid();     // 文件所有者的用户ID
    nfs_stat->st_gid = getgid();     // 文件所有者的组ID
    nfs_stat->st_atime = time(NULL); // 文件最近一次访问时间
    nfs_stat->st_mtime = time(NULL); // 文件最近一次修改时间
    nfs_stat->st_blksize = SZ_BLK;   // 文件所在文件系统的逻辑块大小

    if (is_root)
    {
        nfs_stat->st_size = 0;                 // 文件大小
        nfs_stat->st_blocks = SZ_DISK / SZ_BLK;  // 文件占用的块数
        nfs_stat->st_nlink = 2;                // 文件的连接数
    }

    return NFS_ERROR_NONE;
}

/**
 * @brief 遍历目录项，填充至buf，并交给FUSE输出
 *
 * @param path 相对于挂载点的路径
 * @param buf 输出buffer
 * @param filler 参数讲解:
 *
 * typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
 *				const struct stat *stbuf, off_t off)
 * buf: name会被复制到buf中
 * name: dentry名字
 * stbuf: 文件状态，可忽略
 * off: 下一次offset从哪里开始，这里可以理解为第几个dentry
 *
 * @param offset 第几个目录项？
 * @param fi 可忽略
 * @return int 0成功，否则失败
 */
int nfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
{
    /* TODO:
     * 解析路径，获取目录的Inode，并读取目录项，利用filler填充到buf，可参考/fs/simplefs/nfs.c的nfs_readdir()函数实现 */
    boolean is_find, is_root;
    int cur_dir = offset;

    /* 解析父目录路径 */
    struct nfs_dentry *dentry = nfs_lookup(path, &is_find, &is_root);
    struct nfs_dentry *sub_dentry;
    struct nfs_inode *inode;
    if (is_find)
    {
        inode = dentry->inode_pt;
        /* 根据offset获取到对应的子文件名 */
        sub_dentry = nfs_get_dentry(inode, cur_dir);
        if (sub_dentry)
        {
            /* 直接调用filler来装填结果 */
            filler(buf, sub_dentry->name, NULL, ++offset);
        }
        // if (offset >= 4) {
        //     return -NFS_ERROR_NOTFOUND;
        // }
        return NFS_ERROR_NONE;
    }
    return -NFS_ERROR_NOTFOUND;
}

/**
 * @brief 创建文件
 *
 * @param path 相对于挂载点的路径
 * @param mode 创建文件的模式，可忽略
 * @param dev 设备类型，可忽略
 * @return int 0成功，否则失败
 */
int nfs_mknod(const char *path, mode_t mode, dev_t dev)
{
    /* TODO: 解析路径，并创建相应的文件 */
    int is_find, is_root;

    struct nfs_dentry *lastdentry = nfs_lookup(path, &is_find, &is_root);
    struct nfs_inode *lastinode = lastdentry->inode_pt;
    struct nfs_dentry *dentry;
    char *fname;

    // 文件夹已经存在
    if (is_find)
    {
        return -EEXIST;
    }

    fname = nfs_get_fname(path);

    if (S_ISREG(mode))
    {
        dentry = new_dentry(fname, NFS_FILE, -1);
        nfs_alloc_inode(dentry, NFS_FILE);
    }
    else if (S_ISDIR(mode))
    {
        dentry = new_dentry(fname, NFS_DIR, -1);
        nfs_alloc_inode(dentry, NFS_DIR);
    }

    // 为 lastinode 加一个目录项
    int dirs_per_blk = SZ_BLK / sizeof(struct nfs_dentry_d);
    lastinode->dircnt++;
    if (lastinode->dircnt == 1 || ((lastinode->dircnt - 1) / dirs_per_blk != (lastinode->dircnt - 2) / dirs_per_blk))
    {
        // 需要新的数据块来存增加的目录项
        int blkno = (lastinode->dircnt - 1) / dirs_per_blk;
        if (blkno >= NFS_DATA_PER_FILE)
        {
            return -1;
        }
        char *bpt;
        int bno;
        nfs_alloc_dblk(&bpt, &bno);
        lastinode->datablock[blkno] = bno;
        lastinode->datablock_pt[blkno] = bpt;
    }
    // 插入链表，设置父指针
    dentry->nxt = lastinode->dentry_sons;
    lastinode->dentry_sons = dentry;
    dentry->fa = lastdentry;

    return NFS_ERROR_NONE;
}

/**
 * @brief 修改时间，为了不让touch报错
 *
 * @param path 相对于挂载点的路径
 * @param tv 实践
 * @return int 0成功，否则失败
 */
int nfs_utimens(const char *path, const struct timespec tv[2])
{
    (void)path;
    (void)tv;
    return 0;
}

/******************************************************************************
 * SECTION: 选做函数实现
 *******************************************************************************/
/**
 * @brief 写入文件
 *
 * @param path 相对于挂载点的路径
 * @param buf 写入的内容
 * @param size 写入的字节数
 * @param offset 相对文件的偏移
 * @param fi 可忽略
 * @return int 写入大小
 */
int nfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    /* 选做 */
    return size;
}

/**
 * @brief 读取文件
 *
 * @param path 相对于挂载点的路径
 * @param buf 读取的内容
 * @param size 读取的字节数
 * @param offset 相对文件的偏移
 * @param fi 可忽略
 * @return int 读取大小
 */
int nfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
    /* 选做 */
    return size;
}

/**
 * @brief 删除文件
 *
 * @param path 相对于挂载点的路径
 * @return int 0成功，否则失败
 */
int nfs_unlink(const char *path)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 删除目录
 *
 * 一个可能的删除目录操作如下：
 * rm ./tests/mnt/j/ -r
 *  1) Step 1. rm ./tests/mnt/j/j
 *  2) Step 2. rm ./tests/mnt/j
 * 即，先删除最深层的文件，再删除目录文件本身
 *
 * @param path 相对于挂载点的路径
 * @return int 0成功，否则失败
 */
int nfs_rmdir(const char *path)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 重命名文件
 *
 * @param from 源文件路径
 * @param to 目标文件路径
 * @return int 0成功，否则失败
 */
int nfs_rename(const char *from, const char *to)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 打开文件，可以在这里维护fi的信息，例如，fi->fh可以理解为一个64位指针，可以把自己想保存的数据结构
 * 保存在fh中
 *
 * @param path 相对于挂载点的路径
 * @param fi 文件信息
 * @return int 0成功，否则失败
 */
int nfs_open(const char *path, struct fuse_file_info *fi)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 打开目录文件
 *
 * @param path 相对于挂载点的路径
 * @param fi 文件信息
 * @return int 0成功，否则失败
 */
int nfs_opendir(const char *path, struct fuse_file_info *fi)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 改变文件大小
 *
 * @param path 相对于挂载点的路径
 * @param offset 改变后文件大小
 * @return int 0成功，否则失败
 */
int nfs_truncate(const char *path, off_t offset)
{
    /* 选做 */
    return 0;
}

/**
 * @brief 访问文件，因为读写文件时需要查看权限
 *
 * @param path 相对于挂载点的路径
 * @param type 访问类别
 * R_OK: Test for read permission.
 * W_OK: Test for write permission.
 * X_OK: Test for execute permission.
 * F_OK: Test for existence.
 *
 * @return int 0成功，否则失败
 */
int nfs_access(const char *path, int type)
{
    /* 选做: 解析路径，判断是否存在 */
    return 0;
}

/******************************************************************************
 * SECTION: FUSE入口
 *******************************************************************************/
int main(int argc, char **argv)
{
    int ret;
    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);

    nfs_options.device =
        strdup("/home/students/220110425/user-land-filesystem/ddriver"); // TODO: 请填写你的ddriver设备路径

    if (fuse_opt_parse(&args, &nfs_options, option_spec, NULL) == -1)
        return -1;

    ret = fuse_main(args.argc, args.argv, &operations, NULL);
    fuse_opt_free_args(&args);
    return ret;
}